module dtl.container.vector;

import dtl.details.type;
import std.asserterror;
import std.stdio;

template vector(T, bool Delete=false)
{
  class vector
  {
   public: /// Types
     mixin ValueType!(T);

    class range 
    {
      this(size_type off, T[] r) 
      {
        m_off = off;
        m_r = r;
        debug(DTL) m_mod = mod;
      }

      range opSlice()
      in
      {
        debug(DTL) assert(m_mod == mod);
      }
      body
      {
        return this;
      }

      range opSlice(size_type from, size_type to) 
      in
      {
        debug(DTL) assert(m_mod == mod);
      }
      body
      {
        return new range(m_off + from, m_r[from..to]);
      }

      size_type length() 
      in
      {
        debug(DTL) assert(m_mod == mod);
      }
      body
      {
        return m_r.length;
      }

      T opIndex(size_type i) 
      in
      {
        debug(DTL) assert(m_mod == mod);
      }
      body
      {
        return m_r[i];
      }


      T opIndexAssign(size_type i, T x) 
      in
      {
        debug(DTL) assert(m_mod == mod);
      }
      body
      {
        return m_r[i] = x;
      }

      template opSliceAssign(R, Int) 
      {
        range opSliceAssign(R r, Int from, Int to)
        in
        {
          debug(DTL) assert(m_mod == mod);
          assert(to <= m_r.length);
        }
        body
        {
          return replace(m_off+from,m_off+to,r);
        }
      }

      template opSliceAssign(R) 
      {
        range opSliceAssign(R r)
        in
        {
          debug(DTL) assert(m_mod == mod);
        }
        body
        {
          return replace(m_off,m_off+m_r.length,r);
        }
      }

      int opApply(int delegate(inout T) dg ) 
      in
      {
        debug(DTL) assert(m_mod == mod);
      }
      body
      {
        int result;
        foreach(T t; m_data) 
        {
          debug(DTL) assert(m_mod == mod);
          result = dg(t);
          if(result) break;
        }
        return result;
      }

      int opApply(int delegate(inout size_type i, inout T) dg ) 
      in
      {
        debug(DTL) assert(m_mod == mod);
      }
      body
      {
        int result;
        foreach(size_type i, T t; m_data) 
        {
          debug(DTL) assert(m_mod == mod);
          result = dg(i, t);
          if(result) break;
        }
        return result;
      }

      private final 
      {
        T[] m_r;
        size_type m_off;
        debug(DTL) uint m_mod;
      }

      invariant
      {
        assert(m_r.length + m_off <= m_data.length);
      }

  }

  public: /// Constructors
  this() 
  {
    m_size = 0;
  }

  this(size_t sz, in value_type v)
  {
    m_data.length = m_size = sz;
    m_data[] = v;
  }

  this(in vector v)
  {
    m_data.length =v.m_data.length;
    m_size = v.m_size;
    m_data[] = v.m_data[];
  }

  ~this() {
    static if(Delete){
      for(size_type i = 0; i < m_size; ++i) {
        delete m_data[i];
      }
    }
  }

  public: // accessors
  size_type length() { return m_data.length;}
  void length(size_type i) 
  out
  {
    assert(m_size == i);
    assert(m_data.length >= i);
  }
  body
  {
    grow(i - m_size);
    m_size = i;
  }

  T at(size_type i) 
  in
  {
    assert(i < m_size);
    assert(i >= 0);
  }
  body
  {
    return m_data[i];
  }


  T back()
  {
    return at(m_size-1);
  }

  T back(T what)
  {
    return set(m_size-1,what);
  }

  T front()
  {
    return at(0);
  }

  T front(T what)
  {
    return set(0,what);
  }

  int opApply(int delegate(inout T) dg ) 
  {
    int result;
    debug(DTL) final int this_mod = mod; 
    foreach(T t; m_data) 
    {
      debug(DTL) assert(this_mod == mod);
      result = dg(t);
      if(result) break;
    }
    return result;
  }

  int opApply(int delegate(inout size_type i, inout T) dg ) 
  {
    int result;
    debug(DTL) final int this_mod = mod; 
    foreach(size_type i, T t; m_data) 
    {
      debug(DTL) assert(this_mod == mod);
      result = dg(i, t);
      if(result) break;
    }
    return result;
  }

  public: // Mutators
  T set(size_type i, in T t) 
  in   
  {
    assert(i < m_size);
    assert(i >= 0);
  }
  out
  {
    assert(at(i) == t);
  }
  body
  {
    return m_data[i] = t;
  }

  alias at opIndex;
  alias set opIndexAssign;

  range opSlice() 
  {
    return new range(0,m_data[]);
  }

  void push_back(T t) 
  out 
  {
    assert(back() == t);
  }
  body
  {
    grow(1);
    m_data[m_size++] = t;
  }

  private:
  template replace(R)
  {
    range replace(size_type from, size_type to, R what) 
    in  
    {
      assert(from >= 0);
      assert(from <= to);
      assert(to <= m_data.length);
    }
    body
    {
      difference_type delta = len(what) + from - to;
      grow(delta);
      // move any data that is supposed to move. Overlapping ranges don't work.
      if(delta > 0 && m_size > 1)
        for(size_type i = m_size-1; i >= to; --i)
          m_data[i+delta] = m_data[i];
      else if(delta < 0)
        for(size_type i = to; i < m_size; ++i)
          m_data[i+delta] = m_data[i];
      m_data[from..what.length] = what;
      m_size += delta;
      return new range(from,m_data[from..what.length]);
    }
  }

  template len(R)
  {
    size_type len(R r) { return r.length;}
  }

  template len(R: T)
  {
    size_type len(T r) 
    {
      return 1;
    }
  }
  T[] m_data;
  size_type m_size;
  debug(DTL) 
  {
    uint mod = 0;
  } 

  debug(DTL) 
  {
    // Should be called whenever the size of the vector could change. 
    // Designed to invalidate ranges.
    void modified() 
    {
      mod++;
    }
  }

  void grow(size_type amount=1) 
  out
  {
    assert(m_data.length >= m_size + amount);
  }
  body
  {
    debug(DTL) modified();
    if(amount <= 0) return;
    size_type to_size = (m_size + amount) ;
    size_type next_size = m_data.length;
    while(next_size < to_size) 
    {
      next_size = (next_size * 3) / 2;
    }
    m_data.length = next_size;
    writefln("%d %d %d",m_data.length, m_size, amount);
  }

  invariant 
  {
    assert(m_size <= m_data.length);
  }

  }

}

unittest // Constructors
{
  scope vector!(int) v = new vector!(int);
  scope vector!(int) v2 = new vector!(int)(2,1);
  scope vector!(int) v3 = new vector!(int)(v2);
  assert(v3.length == 2);
  assert(v3.length == v2.length);
}

unittest // foreach
{
  scope vector!(int) v = new vector!(int)(3,1);
  foreach(vector!(int).size_type i, int t; v) {
    assert(t == 1);
  }
  assert(v.back == 1);
  assert(v.front == 1);
}

unittest // slices
{
  scope vector!(int) v = new vector!(int)(10,3);
  vector!(int).range r = v[];
  int[] r2 = new int[5];
  int[] r3 = new int[10];
  r[0..5]= r2;
  r = v[];
  r[] = r3;
  bool failed=false;
  try 
  {
    int[] r4 = new int[2];
    r[] = r4;
  } 
  catch(Error e) 
  {
    failed = true;
  }
  assert(failed,"should have been a length error");
}


unittest // Types
{
  static assert(is(vector!(int).value_type == int));
}

